package cn.sylinx.horm.resource.parse;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cn.sylinx.horm.exception.HORMException;
import cn.sylinx.horm.resource.ReadableClasspathSqlResourceManager;
import cn.sylinx.horm.resource.lexer.Keyword;
import cn.sylinx.horm.resource.liquor.eval.Exprs;

import cn.sylinx.horm.resource.lexer.ELSEToken;
import cn.sylinx.horm.resource.lexer.ELSIFToken;
import cn.sylinx.horm.resource.lexer.ENDToken;
import cn.sylinx.horm.resource.lexer.FUNCToken;
import cn.sylinx.horm.resource.lexer.IFToken;
import cn.sylinx.horm.resource.lexer.INCToken;
import cn.sylinx.horm.resource.lexer.SqlLexer;
import cn.sylinx.horm.resource.lexer.Token;
import cn.sylinx.horm.util.CacheKeyGenerator;
import cn.sylinx.horm.util.Pair;

public class SqlLexerBasedParser {

    private static final Map<String, Pair> LEXERME_CACHE = new HashMap<>();

    private final String sqlKey; // key可能为空
    private final String originalUnparsedSql;
    private String unparsedSql;
    private BitSet bitSet;

    public SqlLexerBasedParser(final String sqlKey, final String unparsedSql) {
        this.sqlKey = sqlKey == null ? CacheKeyGenerator.generateCacheKey(unparsedSql) : sqlKey;
        this.originalUnparsedSql = unparsedSql;

        if (!LEXERME_CACHE.containsKey(sqlKey)) {
            // 不存在，则直接重新分析词法、语法
            doParseLexerme();
        }
    }

    public Pair apply(Map<String, Object> params) {
        if (params == null) {
            params = new HashMap<>(8);
        }
        String sql = parsePlaceholderSql(params);
        String sqlReplaced = OriginalContentPlaceholderParser.INSTANCE.parseSql(sql, params);
        return parseSqlAndParameters(sqlReplaced, params);
    }

    private void doParseLexerme() {

        synchronized (sqlKey.intern()) {

            unparsedSql = parseIncSql(originalUnparsedSql);

            List<Token> lexerList = parseLexerScan();
            if (!lexerList.isEmpty()) {
                bitSet = new BitSet(lexerList.size());
            }
            LEXERME_CACHE.put(sqlKey, Pair.apply(parseLexerme(lexerList), unparsedSql));
        }
    }

    /**
     * 解析inc，并且最终返回所有词位
     *
     * @return Token令牌
     */
    private List<Token> parseLexerScan() {

        List<Token> lexerList = new ArrayList<>();
        if (!unparsedSql.contains(Keyword.KW_IF) && !unparsedSql.contains(Keyword.KW_FUNC)) {
            return lexerList;
        }

        SqlLexer sqlLexer = new SqlLexer(unparsedSql);
        while (!sqlLexer.isEnd()) {
            Token token = sqlLexer.scan();
            if (token != null) {
                lexerList.add(token);
            }
        }
        return lexerList;
    }

    private String parseIncSql(String incSql) {

        if (!incSql.contains(Keyword.KW_INC)) {
            return incSql;
        }

        StringBuilder sb = new StringBuilder(incSql);
        SqlLexer sqlLexer = new SqlLexer(incSql);
        while (!sqlLexer.isEnd()) {
            Token token = sqlLexer.scan();
            if (token instanceof INCToken) {
                INCToken incToken = (INCToken) token;
                String innerSql = parseInc(incToken);
                sb.replace(incToken.getStartIndex(), incToken.getEndIndex(), innerSql);
                return parseIncSql(sb.toString());
            }
        }
        return sb.toString();
    }

    private String parseInc(INCToken incToken) {
        return ReadableClasspathSqlResourceManager.getStatement(incToken.getContent().trim());
    }

    private List<ParsedNode> parseLexerme(List<Token> lexerList) {

        List<ParsedNode> nodeList = new ArrayList<>();

        if (lexerList.isEmpty()) {
            return nodeList;
        }

        for (int i = 0; i < lexerList.size(); ++i) {

            Token token = lexerList.get(i);
            Class<?> tokenClass = token.getClass();

            if (tokenClass == IFToken.class && !bitSet.get(i)) {
                // if开始，组装IfElseBlockNode
                nodeList.add(parseIfElseBlockNode((IFToken) token, i, lexerList));
                bitSet.set(i);
            }

            if (tokenClass == FUNCToken.class && !bitSet.get(i)) {
                nodeList.add(new FUNCNode((FUNCToken) token));
                bitSet.set(i);
            }

            if (tokenClass == INCToken.class && !bitSet.get(i)) {
                nodeList.add(new INCNode((INCToken) token));
                bitSet.set(i);
            }
        }

        return nodeList;
    }

    private IfElseBlockNode parseIfElseBlockNode(IFToken ifToken, int indexIfToken, List<Token> lexerList) {

        IfElseBlockNode ifElseBlockNode = new IfElseBlockNode();

        IFConditionBlock ifConditionBlock = new IFConditionBlock(ifToken);
        ifConditionBlock.setIfElseBlockNode(ifElseBlockNode);
        ifElseBlockNode.setIfConditionBlock(ifConditionBlock);
        List<ELSIFConditionBlock> elsifConditionBlock = new ArrayList<>();
        ifElseBlockNode.setElsifConditionBlock(elsifConditionBlock);

        ConditonBlock preConditonBlock = ifConditionBlock;

        for (int i = indexIfToken + 1; i < lexerList.size(); ++i) {

            Token token = lexerList.get(i);
            Class<?> tokenClass = token.getClass();

            if (tokenClass == IFToken.class && !bitSet.get(i)) {
                // if节点，组装IfElseBlockNode
                preConditonBlock.addParsedNode(parseIfElseBlockNode((IFToken) token, i, lexerList));
                bitSet.set(i);

            } else if (tokenClass == ELSIFToken.class && !bitSet.get(i)) {
                ELSIFConditionBlock elsifCondtionBlock = new ELSIFConditionBlock((ELSIFToken) token);
                elsifCondtionBlock.setIfElseBlockNode(ifElseBlockNode);
                elsifConditionBlock.add(elsifCondtionBlock);

                bitSet.set(i);
                preConditonBlock = elsifCondtionBlock;

            } else if (tokenClass == ELSEToken.class && !bitSet.get(i)) {
                ELSEConditionBlock elseConditionBlock = new ELSEConditionBlock((ELSEToken) token);
                elseConditionBlock.setIfElseBlockNode(ifElseBlockNode);
                ifElseBlockNode.setElseConditionBlock(elseConditionBlock);
                bitSet.set(i);
                preConditonBlock = elseConditionBlock;

            } else if (tokenClass == ENDToken.class && !bitSet.get(i)) {
                ENDConditionBlock endConditionBlock = new ENDConditionBlock((ENDToken) token);
                endConditionBlock.setIfElseBlockNode(ifElseBlockNode);
                ifElseBlockNode.setEndConditionBlock(endConditionBlock);
                bitSet.set(i);
                break;

            } else if (tokenClass == FUNCToken.class && !bitSet.get(i)) {
                preConditonBlock.addParsedNode(new FUNCNode((FUNCToken) token));
                bitSet.set(i);
            }

        }

        return ifElseBlockNode;
    }

    private Pair parseSqlAndParameters(String st, Map<String, Object> params) {
        return PlaceholderParser.INSTANCE_DEFAULT.parseSql(st, params);
    }

    private String parsePlaceholderSql(Map<String, Object> params) {

        Pair p = LEXERME_CACHE.get(sqlKey);
        List<ParsedNode> nodeList = p.getObject(0);
        String sql = p.getObject(1);

        if (nodeList.isEmpty()) {
            return sql;
        }

        this.unparsedSql = sql;
        return parseNodeList(nodeList, params);
    }

    private String parseNodeList(List<ParsedNode> nodeList, Map<String, Object> params) {

        int size = nodeList.size();
        StringBuilder sb = new StringBuilder();

        ParsedNode first = nodeList.get(0);
        ParsedNode lastParsedNode = nodeList.get(size - 1);

        sb.append(unparsedSql, 0, first.getStartIndex());

        for (int i = 0; i < size; ++i) {

            ParsedNode node = nodeList.get(i);

            if (node.getClass() == FUNCNode.class) {
                // 函数
                sb.append(parseFUNCNodeContent((FUNCNode) node, params));
            } else if (node.getClass() == IfElseBlockNode.class) {
                // if-else 块
                sb.append(parseIfElseBlockNodeContent((IfElseBlockNode) node, params));
            }

            int j = i + 1;
            if (j < size) {
                ParsedNode next = nodeList.get(j);
                sb.append(unparsedSql, node.getEndIndex(), next.getStartIndex());
            }

        }

        sb.append(unparsedSql.substring(lastParsedNode.getEndIndex()));

        return sb.toString();
    }

    private String parseNodeListPart(int startIndex, int endIndex, List<ParsedNode> nodeList,
                                     Map<String, Object> params) {

        int size = nodeList.size();
        StringBuilder sb = new StringBuilder();

        ParsedNode first = nodeList.get(0);
        ParsedNode lastParsedNode = nodeList.get(size - 1);

        sb.append(unparsedSql, startIndex, first.getStartIndex());

        for (int i = 0; i < size; ++i) {

            ParsedNode node = nodeList.get(i);

            if (node.getClass() == FUNCNode.class) {
                // 函数
                sb.append(parseFUNCNodeContent((FUNCNode) node, params));

            } else if (node.getClass() == IfElseBlockNode.class) {
                // if-else 块
                sb.append(parseIfElseBlockNodeContent((IfElseBlockNode) node, params));
            }

            int j = i + 1;
            if (j < size) {
                ParsedNode next = nodeList.get(j);
                sb.append(unparsedSql, node.getEndIndex(), next.getStartIndex());
            }

        }

        sb.append(unparsedSql, lastParsedNode.getEndIndex(), endIndex);

        return sb.toString();
    }

    private String parseIfElseBlockNodeContent(IfElseBlockNode ifElseBlockNode, Map<String, Object> params) {

        IFConditionBlock ifCondition = ifElseBlockNode.getIfConditionBlock();
        IFToken ifToken = ifCondition.getIfToken();

        List<ELSIFConditionBlock> elsifConditionBlockList = ifElseBlockNode.getElsifConditionBlock();
        ELSEConditionBlock elseConditionBlock = ifElseBlockNode.getElseConditionBlock();
        ENDConditionBlock endConditionBlock = ifElseBlockNode.getEndConditionBlock();

        if (endConditionBlock == null) {
            throw new RuntimeException("invalid sql");
        }

        StringBuilder sb = new StringBuilder();

        if (matchCondition(ifToken, params)) {// 判断if
            // 1、符合if分支
            List<ParsedNode> nodes = ifCondition.getNodes();
            int toIndex = getIfTokenNextIndex(ifElseBlockNode);

            if (nodes == null || nodes.isEmpty()) {
                // 不存在孩子节点
                sb.append(unparsedSql, ifToken.getEndIndex(), toIndex);
            } else {
                // 存在节点
                sb.append(parseNodeListPart(ifToken.getEndIndex(), toIndex, nodes, params));
            }

        } else {

            ELSIFConditionBlock elseifConditionBlock = getMatchedELSIFToken(elsifConditionBlockList, params);
            if (elseifConditionBlock != null) { // 判断 elsif
                // 2、符合elsif分支
                ELSIFToken elsifToken = elseifConditionBlock.getElsifToken();
                int toIndex = getElsifTokenNextIndex(ifElseBlockNode, elseifConditionBlock);

                List<ParsedNode> nodes = elseifConditionBlock.getNodes();
                if (nodes == null || nodes.isEmpty()) {
                    // 不存在孩子节点
                    sb.append(unparsedSql, elsifToken.getEndIndex(), toIndex);
                } else {
                    // 存在节点
                    sb.append(parseNodeListPart(elsifToken.getEndIndex(), toIndex, nodes, params));
                }

            } else if (elseConditionBlock != null) { // 判断else
                // 3、符合else分支
                ELSEToken elseToken = elseConditionBlock.getElseToken();
                int toIndex = getElseTokenNextIndex(ifElseBlockNode);
                List<ParsedNode> nodes = elseConditionBlock.getNodes();
                if (nodes == null || nodes.isEmpty()) {
                    // 不存在孩子节点

                    sb.append(unparsedSql, elseToken.getEndIndex(), toIndex);
                } else {
                    // 存在节点
                    sb.append(parseNodeListPart(elseToken.getEndIndex(), toIndex, nodes, params));
                }
            }
        }

        return sb.toString();
    }

    private int getElseTokenNextIndex(IfElseBlockNode ifElseBlockNode) {
        // end
        return ifElseBlockNode.getEndConditionBlock().getEndToken().getStartIndex();
    }

    private int getElsifTokenNextIndex(IfElseBlockNode ifElseBlockNode, ELSIFConditionBlock elseifConditionBlock) {

        List<ELSIFConditionBlock> elsifConditionBlockList = ifElseBlockNode.getElsifConditionBlock();
        int targetIndex = 0;
        int size = elsifConditionBlockList.size();
        for (int i = 0; i < size; ++i) {
            if (elsifConditionBlockList.get(i) == elseifConditionBlock) {
                targetIndex = i + 1;
                break;
            }
        }
        if (targetIndex < size) {
            return elsifConditionBlockList.get(targetIndex).getElsifToken().getStartIndex();
        }

        ELSEConditionBlock elseConditionBlock = ifElseBlockNode.getElseConditionBlock();
        if (elseConditionBlock != null) {
            // elsif 不存在，else 存在
            return elseConditionBlock.getElseToken().getStartIndex();
        }

        // end
        return ifElseBlockNode.getEndConditionBlock().getEndToken().getStartIndex();
    }

    private int getIfTokenNextIndex(IfElseBlockNode ifElseBlockNode) {

        List<ELSIFConditionBlock> elsifConditionBlockList = ifElseBlockNode.getElsifConditionBlock();
        if (!elsifConditionBlockList.isEmpty()) {
            // 存在elsif
            return elsifConditionBlockList.get(0).getElsifToken().getStartIndex();
        }

        ELSEConditionBlock elseConditionBlock = ifElseBlockNode.getElseConditionBlock();
        if (elseConditionBlock != null) {
            // elsif 不存在，else 存在
            return elseConditionBlock.getElseToken().getStartIndex();
        }

        // end
        return ifElseBlockNode.getEndConditionBlock().getEndToken().getStartIndex();
    }

    private ELSIFConditionBlock getMatchedELSIFToken(List<ELSIFConditionBlock> elsifConditionBlockList,
                                                     Map<String, Object> params) {

        for (ELSIFConditionBlock elsifConditionBlock : elsifConditionBlockList) {
            if (matchCondition(elsifConditionBlock.getElsifToken(), params)) {
                return elsifConditionBlock;
            }
        }

        return null;
    }

    private boolean matchCondition(ELSIFToken elsifToken, Map<String, Object> params) {
        return testCondition(elsifToken.getContent(), params);
    }

    private boolean matchCondition(IFToken ifToken, Map<String, Object> params) {
        return testCondition(ifToken.getContent(), params);
    }

    private boolean testCondition(String express, Map<String, Object> params) {
        return SystemConditionParser.testCondition(express, params);
    }

    private String parseFUNCNodeContent(FUNCNode funcNode, Map<String, Object> params) {
        FUNCToken funcToken = funcNode.getFuncToken();
        return parseFUNCContent(funcToken.getContent(), params);
    }

    private String parseFUNCContent(String express, Map<String, Object> params) {
        try {
            return (String) Exprs.eval(express, params);
        } catch (InvocationTargetException e) {
            throw new HORMException(e);
        }
    }

}
